require 'win32/process'
require 'Win32API'
require 'thread'
require 'rbconfig'
require 'win32/open3'
require 'countdownlatch'
require 'cfgtools.rb'

$camstudio_cl = get_config_value("camstudio_cl", "camstudio_cl.exe")
$camstudio_codec = get_config_value("camstudio_codec", 1)
$mkvmerge = get_config_value("mkvmerge", "mkvmerge.exe")
$ffmpeg = get_config_value("ffmpeg", "ffmpeg.exe")
$write_log = Integer(get_config_value("write_log", 1))
$screencapture = get_config_value("screencapture", "screencapture.exe")
$capture_screenshots = get_config_value("capture_screenshots", true) == true

module RuSalad
  module Recorder
    class Win

      def start(workdir, filename, screenshot_filename, logfile = nil) # Filename should be passed without extension
        if @screenshot_filename != screenshot_filename
            @screenshot_filename = screenshot_filename
            @stepno = 1
        end
        $logfile = logfile ? logfile : File.join(workdir, "#{filename}.log")
        @workdir = workdir
        op = Win32API.new('kernel32', 'OpenProcess', ['L', 'I', 'L'], 'L')
        fail "CamStudio_cl executable '#{$camstudio_cl}' not found" unless File.exists?($camstudio_cl)
        fail "screencapture executable '#{$screencapture}' not found" unless File.exists?($screencapture)
        fail "ffmpeg executable '#{$ffmpeg}' not found" unless File.exists?($ffmpeg)
        fail "mkvmerge executable '#{$mkvmerge}' not found" unless File.exists?($mkvmerge)
        filenamewin = "#{workdir}/#{filename}".gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)

        cmd = "\"#{$camstudio_cl}\" -codec #{$camstudio_codec} -outfile \"#{filenamewin}.avi\""
        log "Executing #{cmd}"
        @stdin_o, @stdout_i, @stderr_i, proc_id = Open4.popen4(cmd);
        @proc_handle = op.call(0x00100400, 1, proc_id)

        latch = CountDownLatch.new 1
        @camreader = Thread.new do
          log "CAMSTUDIO Started"
          @stdout_i.each do |line|
            log "CAMSTUDIO: #{line}"
            latch.countdown! if (line.index("Enter any key to stop recording"))
          end
          @stdout_i.close
          log "CAMSTUDIO Exited"
          Thread.exit
        end
        @camreader2 = Thread.new do
          log "CAMSTUDIO Started (2)"
          @stderr_i.each do |line|
            log "CAMSTUDIO (2): #{line}"
          end
          @stderr_i.close
          log "CAMSTUDIO (2) Exited"
          Thread.exit
        end
        if !(latch.wait 10)
          fail "Camstudio did not started recording for some reason. See log for details" 
        end
      end

      def step()
        if $capture_screenshots
          fn = File.join(@workdir, "#{@screenshot_filename}_#{@stepno}.png").gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
          @stepno = @stepno + 1
          log "Executing: #{$screencapture} #{fn}"
          if !(system "#{$screencapture} #{fn}")
            fail "ScreenCapture did not work for some reason" 
          end
        end
      end

      def stop()
        sleep 1
        log "Stopping camstudio..."
        @stdin_o.puts "\n"
        @stdin_o.flush
        @stdin_o.close
        wait_process_handle(@proc_handle)
        @camreader.join
        @camreader2.join
      end

      def package(workdir, sourcefilename, targetfilename, logfile = nil) # Filename should be passed without extension
        $logfile = logfile ? logfile : File.join(workdir, "#{sourcefilename}.log") unless logfile
        moviename = "#{workdir}/#{targetfilename}"
        moviefilename = File.join(moviename.split('/'));
        moviefilenamewin = moviefilename.gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)

        sourcefilenamewin = "#{workdir}/#{sourcefilename}".gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)

        # Encode FLV
        cmd_flv = "\"#{$ffmpeg}\" -i \"#{sourcefilenamewin}.avi\" -vcodec libx264 \"#{moviefilenamewin}.flv\""
        proc_flv = run_redirect(cmd_flv)

        # Encode x264
        cmd_x264 = "\"#{$ffmpeg}\" -i \"#{sourcefilenamewin}.avi\" -vcodec libx264 \"#{sourcefilenamewin}264.avi\""
        proc_x264 = run_redirect(cmd_x264)

        raise "FLV encoding failed" unless wait_redirect(proc_flv) == 0
        raise "X264 encoding failed" unless wait_redirect(proc_x264) == 0

        cmd_mkv = "\"#{$mkvmerge}\" -o \"#{moviefilenamewin}.mkv\" --compression -1:none \"#{sourcefilenamewin}264.avi\" --compression -1:none \"#{sourcefilenamewin}.srt\""
        proc_mkv = run_redirect cmd_mkv
        raise "MKV assembly failed" unless wait_redirect(proc_mkv) == 0

        return true
      end

      def run_redirect(cmd)
        op = Win32API.new('kernel32', 'OpenProcess', ['L', 'I', 'L'], 'L')
        log "Executing #{cmd}"
        stdin_o, stdout_i, stderr_i, proc_id = Open4.popen4(cmd);
        proc_handle = op.call(0x00100400, 1, proc_id)

        logger_e = Thread.new do
          stderr_i.each do |line|
            log "EXEC ERR: #{line}"
          end
          stderr_i.close
          Thread.exit
        end

        logger_o = Thread.new do
          stdout_i.each do |line|
            log "EXEC OUT: #{line}"
          end
          stdout_i.close
          Thread.exit
        end

        stdin_o.close

        return Hash[
          :proc_id => proc_id,
          :proc_handle => proc_handle,
          :logger_o => logger_o,
          :logger_e => logger_e
        ]
      end

      def wait_redirect(h)
        v = wait_process_handle(h[:proc_handle])
        h[:logger_o].join
        h[:logger_e].join
        return v
      end

      def wait_process(pid)
        op = Win32API.new('kernel32', 'OpenProcess', ['L', 'I', 'L'], 'L')
        ph = op.call(0x00100400, 1, pid)
        return -1 if ph == -1
        return wait_process_handle(ph)
      end

      def wait_process_handle(ph)
        wfso = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'I')
        gle = Win32API.new('kernel32', 'GetLastError', [], 'I')
        ch = Win32API.new('kernel32', 'CloseHandle', ['L'], 'I')
        gecp = Win32API.new('kernel32', 'GetExitCodeProcess', ['L', 'P'], 'I')

        res = 258 # WAIT_TIMEOUT
        while res == 258
          res = wfso.call(ph, 10)
        end
        if (res == -1)
          err = gle.call()
          ch.call(ph)
          raise "Error #{err}"
        end
        exitcode = "xxxx"
        if gecp.call(ph, exitcode) == 0
          err = gle.call()
          ch.call(ph)
          raise "Error #{err}"
        end
        ch.call(ph)
        exitcode = exitcode.unpack("L")[0];
        return exitcode;
      end

      def log(s)
        if $write_log != 0
          log_file = File.exists?($logfile) ? File.new($logfile, 'a') : File.new($logfile, 'w')
          log_file.puts("#{Time.now} #{s}")
          log_file.close
        end
      end
    
    end
  end
end
